#pragma once

#include <string>
#include <algorithm>
#include <functional>
#include <vector>
//#define NEWS_BENCHMARK 1
#include "NewsBenchMark.h"
#include "XMLNodeCache.h"
#include "util.h"//For trim

XMLNodeCache g_cache;

#define DEFAULT_LEVEL 1

class XMLNode{
private:
    enum state
    {
        start = 0,
        init,
        declare,
        nodeName,
        nodeName2,
        val,
        tag,
        tag2,
        pi,
        cdata,
        startClose,
        childNode,
        nodeClose1,
        nodeClose2,
        attrName,
        attrVal1,
        attrVal2,
        attrVal3,
        invalid
    };
public:
    enum relation{
        next = 0,
        child,
        sibling,
        decsendant,
        sibling_under,
        max = 16
    };

    XMLNode( char* msg ){m_xml = msg;}
    XMLNode& Next( const std::string& name, relation rel = next, char* parent = NULL );
    XMLNode& Child( const std::string& name ){ return Next(name, child);}
    XMLNode& Sibling( const std::string& name ){ return Next(name, sibling); }
    XMLNode& Decsendant( const std::string& name ){return Next(name, decsendant);}
    XMLNode& SiblingUnderParent( const std::string& name, char* parent){return Next(name, sibling_under, parent); }
    XMLNode& Attr( const std::string& name, std::string& val );

    operator bool() const { return m_xml != NULL; }

    char* Pointer(){return m_xml;}

    std::string Name(){return m_name;}

    //Copy the current node's content into content
    //If no content at all, then return ""
    XMLNode& Content( std::string& content )
    {
        if( m_xml == NULL )
        {
            return *this;
        }

        TIMESUM("content", content_time);

        std::string node_content;
        int level = 0;
        state st = init;
        char* cur = m_xml;

        while( *cur )
        {
            switch( st )
            {
            case init:
                switch( *cur )
                {
                case '<':
                    st = tag;
                    continue; //We start a tag
                default:
                    break;
                }
                break;
            case nodeName:
                switch (*cur )
                {
                case ' ':
                case '\t':
                    st = startClose;
                    break; 
                case '>':
                    st = childNode;
                    break;
                case '/':
                    content = "";
                    return *this; 
                    break;
                }
                break;
            case val:
                switch( *cur )
                {
                case '<':
                    content = node_content;
                    goto END;
                default:
                    node_content += *cur;
                    break;
                }
                break;
            case tag:
                switch( *cur )
                {
                case '<':
                    st = tag2;
                    break;
                default:
                    break;
                }
                break;
            case tag2:
                switch( *cur )
                {
                case '!':
                    //st = pi;
                    st = nodeClose2;
                    //continue;//We don't process pi
                    break;
                case '?':
                    //st = declare;
                    st = nodeClose2;
                    //continue;
                    break;
                case '/':
                    st = nodeClose1;
                    //continue;
                    break;
                default:
                    level++;
                    st = nodeName;
                    //continue;
                    continue; //Don't move the cursor
                }
            case pi:
                break;
            case startClose:
                switch( *cur )
                {
                case '>':
                    st = childNode;
                    break;
                case '/':
                    st = nodeClose1;
                    break;
                case ' ':
                case '\t':
                    break;
                default:
                    st = attrName;
                    break;
                }
                break;
            case childNode:
                switch( *cur )
                {
                case ' ':
                case '\t':
                case '\n':
                    break;
                case '<':
                    st = tag2;
                    break;
                default:
                    //this is the point for val;
                    st = val;
                    continue;
                }
                break;
            case nodeClose1:
                level--;
                if( level == 0)
                    goto END;
                st = nodeClose2;
                continue;
            case nodeClose2:
                switch(*cur)
                {
                case '>':
                    st = tag;
                    break;
                default:
                    ; //Default do nothing.
                    break;
                }
                break;
            case attrName:
                switch( *cur )
                {
                case ' ':
                case '\t':
                    break;
                case '=':
                    st = attrVal1;
                    break;

                default:
                    break;
                }
                break;
            case attrVal1:
                switch( *cur )
                {
                case ' ':
                case '\t':
                    break;
                case '\'':
                    st = attrVal2;
                    break;
                case '"':
                    st = attrVal3;
                    break;
                }
                break;
            case attrVal2:
                switch( *cur )
                {
                case '\'':
                    st = startClose;
                    break;
                default:
                    break;
                }
                break;
            case attrVal3:
                switch( *cur )
                {
                case '"':
                    st = startClose;
                    break;
                default:
                    break;
                }
                break;
            case invalid:
                break;
            }
            cur++;
        }
END:
        trim(content);
        return *this;
    }

    XMLNode& Reset(){ m_name.erase(); m_xml = NULL; }
private:
    char*                       m_xml;  //This is the pointer which points to the cur pos
    std::string                 m_name;
};

void Wchar2Utf8( wchar_t wChar, std::string& utf8out )
{
    typedef unsigned char   uint8;
    if ( wChar < 0x80 ) {
        utf8out  += static_cast<uint8>(wChar);
    } else if ( wChar < 0x800 ) {
        utf8out += static_cast<uint8>((0xC0 | wChar >> 6));
        utf8out += static_cast<uint8>((0x80 | wChar       & 0x3F));
    } else if ( wChar < 0x10000 ) {
        utf8out += static_cast<uint8>((0xE0 | wChar >> 12));
        utf8out += static_cast<uint8>((0x80 | wChar >> 6  & 0x3F));
        utf8out += static_cast<uint8>((0x80 | wChar       & 0x3F));
    } else if ( wChar < 0x200000 ) {
        utf8out += static_cast<uint8>((0xF0 | wChar >> 18));
        utf8out += static_cast<uint8>((0x80 | wChar >> 12 & 0x3F));
        utf8out += static_cast<uint8>((0x80 | wChar >> 6  & 0x3F));
        utf8out += static_cast<uint8>((0x80 | wChar       & 0x3F));
    }
}

bool DecodeEntity( char*& cur, std::string& val )
{
    int state = 0; //0 for nothing, 1 for entity start
    //2 for number start, 3 for 10 based
    //4 for 8 based, 5 for 16 based
    //6 for name start, 8 for end
    //9 for err
    int base = 10;
    wchar_t wch = 0;
    std::string name;

    while( true )
    {
        switch( state )
        {
        case 0:
            switch( *cur )
            {
            case '&':
                state = 1;
                break;
            default:
                state = 9;
                break;
            }
        case 1:
            switch( *cur )
            {
            case '#':
                state = 2;
                break;
            default:
                state = 6;
                break;

            }
            break;
        case 2:
            switch( *cur )
            {
            case 'x':
            case 'X':
                base = 16;
                break;
            case 'A':
            case 'a':
                wch *= base;
                wch += 10;
                break;
            case 'B':
            case 'b':
                wch *= base;
                wch += 11;
                break;
            case 'C':
            case 'c':
                wch *= base;
                wch += 12;
                break;
            case 'D':
            case 'd':
                wch *= base;
                wch += 13;
                break;
            case 'E':
            case 'e':
                wch *= base;
                wch += 14;
                break;
            case 'F':
            case 'f':
                wch *= base;
                wch += 15;
                break;
            case ';':
                //It is closed
                Wchar2Utf8( wch, val );
                state = 8;
                break;
            default:
                wch *= base;
                wch += *cur - '0';
                break;       
            }
            break;
        case 6:
            //The predefined entity  lt gt amp apos quot or internal defined entity
            //we look this up by a map

            switch( *cur )
            {
            case ';':
                
                break;
            default:
                name += *cur;
                break;
            }
            break;
        case 8:
            goto END;
        case 9:
            return false;
        default:
            break;
        }
        cur++;
    }
END:
    return true;
}

inline XMLNode& XMLNode::Attr(const std::string& name, std::string& val)
{
    TIMESUM( "attr", att_time );
    if( m_xml == NULL )
    {
        val.clear();
        return *this;
    }

    state st = init;

    char* cur = m_xml;

    std::string attr_name;

    std::string attr_val;
    attr_val.reserve(64);

    while( *cur )
    {
        switch( st )
        {
        case init:
            switch( *cur )
            {
            case '<':
                st = tag;
                continue; //We start a tag
            default:
                break;
            }
        case nodeName:
            switch (*cur)
            {
            case ' ':
            case '\t':
                st = startClose;
                break;
            case '>':
            case '/':
                goto END;
                break;
            default:
                break;
            }
        case tag:
            switch( *cur )
            {
            case '<':
                st = tag2;
                break;
            default:
                break;
            }
            break;
        case tag2:
            switch( *cur )
            {
            case '!':
                //st = pi;
                st = nodeClose2;
                break;
            case '?':
                //st = declare;
                st = nodeClose2;
                break;
            case '/':
                st = nodeClose1;
                break;
            default:
                st = nodeName;
                continue; //Don't move the cursor
            }
            break;
        case startClose:
            switch( *cur )
            {
            case '>':
            case '/':
                goto END;
                break;
            case ' ':
            case '\t':
            case '\n':
                break;
            default:
                st = attrName;
                continue;
            }
            break;
        case nodeClose1:
        case nodeClose2:
            goto END;
        case attrName:
            switch( *cur )
            {
            case ' ':
            case '\t':
            case '\n':
                break;
            case '=':
                trim(attr_name);
                st = attrVal1;
                break;
            default:
                attr_name += *cur;
                break;
            }
            break;
        case attrVal1:
            switch( *cur )
            {
            case ' ':
            case '\t':
                break;
            case '\'':
                st = attrVal2;
                break;
            case '"':
                st = attrVal3;
                break;
            }
            break;
        case attrVal2:
            switch( *cur )
            {
            case '\'':
                if( attr_name == name )
                {
                    val = attr_val; 
                    goto END;
                }
                else
                {
                    attr_name.clear();
                    attr_val.clear();
                }
                st = startClose;
                break;
            default:
                attr_val += *cur;
                break;
            }
            break;
        case attrVal3:
            switch( *cur )
            {
            case '"':
                if( attr_name == name )
                {
                    val = attr_val; 
                    goto END;
                }
                else
                {
                    attr_name.clear();
                    attr_val.clear();
                }
                st = startClose;
                break;
            case '&':
                //we need to do the entity decode
                DecodeEntity( cur, attr_val );
                break;
            default:
                attr_val += *cur;
                break;
            }
            break;
        }
        cur++;
    }
END:
    return *this;
}


XMLNode& XMLNode::Next( const std::string& name, relation rel, char* parent) //Goes to next brother with name
{


    if( m_xml == NULL)
    {
        return *this;
    }

    TIMESUM("Next", next_time);

    //TIMESUMSTART("Next Init");
    char* cur = m_xml; //The cursor, moving as parsing

    int level = DEFAULT_LEVEL;
    int curLevel = 0;
    int count = 0;
    bool cache_enable = true;
    bool build_cache = false;

    //If cur not under cache coverage, then we disable the cache, that means our parse can't get the cache help
    if( !g_cache.InRange( cur ) )
        cache_enable = false;

    //For test only
    //cache_enable = false;

    if( cache_enable )//if cache enable, try to get the current states    if start from the beginning, then we should set build_cache to true;
    {
        XMLNodeCache::iterator front = g_cache.begin();
        if( front != g_cache.end() )
        {
            if( cur <= front->point )
            {
                cur = front->point;
                level = front->level;
            }
            else
            {
                XMLNodeCache::iterator cur_pos;
                XMLNodeCache::err_code res = g_cache.LocateRecord( XMLNodeCache::record_type( cur, 0 ), cur_pos );
                switch( res )
                {
                case XMLNodeCache::err_ok:
                    level = cur_pos->level;
                    break;
                case XMLNodeCache::err_next:
                    //This should not happen
                    break;
                case XMLNodeCache::err_node_na:
                    //We are in the wrong place, disable cache
                    cache_enable = false;
                    level = 0;
                    break;
                }
            }
        }
        else
        {
            build_cache = true;
            level = DEFAULT_LEVEL;
        }
    }

    state st = init;

    char* tagstart = cur; //A temperary tagstart as a reminder for current tag's start point. Used for assing to m_xml when we located the tag


    //The accepted level is level >= minTarLv & level <= maxTarLv
    //The fail level is level >= 
    int minTarLv = 0, maxTarLv = 0;
    int minFailLv = 0, maxFailLv = 0;



    switch( rel )
    {
    case next:
        minTarLv = maxTarLv = level + 1;
        minFailLv = maxFailLv = level == 0 ? 0 : level - 1;
        break;
    case sibling:
        //Accept all and fail at nothing
        minTarLv = 0;
        maxTarLv = 65535;
        minFailLv = maxFailLv = 0;
        break;
    case decsendant:
        minTarLv = level + 1;
        maxTarLv = 65535;
        minFailLv = 0;
        maxFailLv = level;
        break;
    case child:
        minTarLv = level + 1;
        maxTarLv = level + 1;
        minFailLv = 0;
        maxFailLv = level;
        break;
    case sibling_under:
        //New behavior, used to find the sibling node under the parent
        //It is very useful when go through nodes under some specific 
        //Parent node.

        XMLNodeCache::record_type parentNode;
        parentNode.point = parent;
        if( parent != NULL && g_cache.RetrieveRecord( parentNode ) == XMLNodeCache::err_ok )
        {
            minTarLv = 0;
            maxTarLv = 65535;
            minFailLv = 0;
            maxFailLv = parentNode.level;
        }else{
            minTarLv = 0;
            maxTarLv = 65535;
            minFailLv = 0;
            maxFailLv = 0;
        }
        break;

    }

    std::string node_name;

    bool find = false;

    level --;

    //TIMESUMSTOP("Next Init");

    //TIMESUMSTART("Next state");
    while( *cur )
    {
        switch( st )
        {
        case init:
            switch( *cur )
            {
            case '<':
                st = tag;
                continue; //We start a tag
            case 0:
                goto END;
            default:
                break;
            }
            break;
        case tag:
            switch( *cur )
            {
            case '<':
                tagstart = cur;
                st = tag2;
                break;
            default:
                break;
            }
            break;
        case tag2:
            switch( *cur )
            {
            case '!':
                st = pi;
                count++;
                break;
            case '?':
                //st = declare;
                count++;
                st = nodeClose2;
                //continue;
                break;
            case '/':
                count++;
                st = nodeClose1;
                //continue;
                break;
            default:
                count++;
                level++;
                if( build_cache )
                {
                    g_cache.PushBack( XMLNodeCache::record_type( tagstart, level ) );
                }
                //we check the level here
                if( level >= minFailLv && level <= maxFailLv && count != 1)
                    goto END;
                st = nodeName;
                continue; //Don't move the cursor
            }
            break;
        case val:
            switch( *cur )
            {
            case '<':
                tagstart = cur;
                st = tag2;
                break;
            default:
                break;
            }
            break;
        case nodeName:
            switch (*cur)
            {
            case ' ':
            case '\t':
            case '\n':
            case '>':
            case '/':
                st = nodeName2;
                continue;
                break;
            default:
                node_name += *cur;
                break;
            }
            break;
        case nodeName2:
            if( count > 1 && 
                (node_name == name || name.length() == 0) &&
                ( level >= minTarLv && level <= maxTarLv )
                )
            {
                goto FIND;
            }else if(  cache_enable && !build_cache ){
                switch( g_cache.GetHint( tagstart, level, minTarLv, maxTarLv, minFailLv, maxFailLv, cur, level ) )
                {
                case XMLNodeCache::err_build_cache:
                    build_cache = true;
                    level--;
                    continue;
                    break;
                case XMLNodeCache::err_ok:
                    level--;
                    break;
                case XMLNodeCache::err_node_na:
                    //We met problem here
                    ;
                    break;
                default:
                    break;
                }
                st = val;
                node_name.clear();
                continue;
            }
            node_name.clear();
            switch( *cur )
            {
            case ' ':
            case '\t':
            case '\n':
                st = startClose;
                break;
            case '/':
                st = nodeClose1;
                break;
            case '>':
                st = val;
                break;
            default:
                break;                    
            }
            break;
        case pi:
            if( *cur == '[' && *(cur+1) == 'C' && *(cur+2) == 'D'
                && *(cur+3) == 'A' && *(cur+4) == 'T' && *(cur+5) == 'A' && *(cur+6) == '[')
            {
                cur+= 7; //strlen("[CDATA[");
                st = cdata;
            }
            else
            {
                st = nodeClose2;
            }
            break;
        case cdata:
            if( *cur == ']' && *(cur+1) == ']' && *(cur + 1) == '>' )
            {
                cur += 3; //strlen("]]>");
                st = val;
            }
            else
            {
                ;
                //Add the curent ch to content
            }
            break;
        case startClose:
            switch( *cur )
            {
            case '>':
                st = val;
                break;
            case '/':
                st = nodeClose1;
                break;
            case ' ':
            case '\t':
            case '\n':
                break;
            default:
                st = attrName;
                continue;
            }
            break;

        case nodeClose1:
            level--;
            st = nodeClose2;
            continue;

        case nodeClose2:
            switch(*cur)
            {
            case '>':
                st = val;
                break;
            default:
                ; //Default do nothing.
                break;
            }
            break;
        case attrName:
            switch( *cur )
            {
            case ' ':
            case '\t':
            case '\n':
                break;
            case '=':
                st = attrVal1;
                break;
            default:
                break;
            }
            break;
        case attrVal1:
            switch( *cur )
            {
            case ' ':
            case '\t':
            case '\n':
                break;
            case '\'':
                st = attrVal2;
                break;
            case '"':
                st = attrVal3;
                break;
            }
            break;
        case attrVal2:
            switch( *cur )
            {
            case '\'':
                st = startClose;
                break;
            default:
                break;
            }
            break;
        case attrVal3:
            switch( *cur )
            {
            case '"':
                st = startClose;
                break;
            default:
                break;
            }
            break;
        case invalid:
            break;
        }
        //ShowStateName( st );
        cur++;
    }
    goto END;
FIND:
    m_name = node_name;
    m_xml = tagstart;
    find = true;
END:
    //TIMESUMSTOP("Next state");
    if(!find)
        m_xml = NULL;
    return *this;
}
